package tool

import (
	"context"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"git.900sui.cn/kc/kcgin"
	"git.900sui.cn/kc/redis"
	"git.900sui.cn/kc/rpcPay/constkey"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"
)

//发起微信请求结构体
type WXClient struct {
	cid              int    //区域id
	wxPublicKeyPath  string //证书地址
	wxPrivateKeyPath string //私钥地址
	wxSerialNo       string //微信商户API证书
	mchid            string //直连商户号
	appId            string //微信appid
	apiV3Key         string //微信秘钥
	certUrl          string //微信获取获取平台证书url
	certExpire       string //证书token有效期
}

//微信app预支付返回结构体
type WXprepay struct {
	PrepayId string `json:"prepay_id"` //预支付
}

//微信唤起支付结构体
type WXUpPay struct {
	Appid     string `json:"appid"`     //应用id
	Partnerid string `json:"partnerid"` //商户号
	Prepayid  string `json:"prepayid"`  //预支付交易会话ID
	Package   string `json:"package"`   //订单详情扩展字符串 暂填写固定值Sign=WXPay
	Noncestr  string `json:"noncestr"`  //随机字符串
	Timestamp string `json:"timestamp"` //时间戳
	Sign      string `json:"sign"`      //签名
}

//微信回调通知结构体
type WXNotify struct {
	Id           string   `json:"id"`            //通知ID
	CreateTime   string   `json:"create_time"`   //通知创建时间
	EventType    string   `json:"event_type"`    //通知类型
	ResourceType string   `json:"resource_type"` //通知数据类型
	Resource     resource `json:"resource"`      //通知数据
	Summary      string   `json:"summary"`       //回调摘要
}

//微信回调通知数据
type resource struct {
	Algorithm       string `json:"algorithm"`       //加密算法类型
	Ciphertext      string `json:"ciphertext"`      //数据密文
	Associated_data string `json:"associated_data"` //附加数据
	Original_type   string `json:"original_type"`   //原始类型
	Nonce           string `json:"nonce"`           //随机串
}

//微信回调header
type WXHeader struct {
	WechatpaySerial    string `json:"Wechatpay-Serial"`    //微信支付的平台证书序列号
	WechatpayTimestamp string `json:"Wechatpay-Timestamp"` //应答时间戳。
	WechatpayNonce     string `json:"Wechatpay-Nonce"`     //应答随机串
	WechatpaySignature string `json:"Wechatpay-Signature"` //应答签名
}

//微信获取证书数据
type WXGetCertRes struct {
	Data []WXGetCertData `json:"data"`
}

//微信获取证书data
type WXGetCertData struct {
	EffectiveTime      string   `json:"effective_time"`
	EncryptCertificate resource `json:"encrypt_certificate"`
	ExpireTime         string   `json:"expire_time"`
	SerialNo           string   `json:"serial_no"`
}

const (
	//上海
	CHANNELAREA = 321
)

//获取微信请求客户端
func GetWXClient(cid int) (*WXClient, error) {
	if cid == CHANNELAREA {
		client := &WXClient{
			cid:              CHANNELAREA,
			wxPublicKeyPath:  kcgin.AppConfig.String("pingan.shWXpublicKeyPath"),
			wxPrivateKeyPath: kcgin.AppConfig.String("pingan.shWXprivateKeyPath"),
			wxSerialNo:       kcgin.AppConfig.String("pingan.shWXserialNo"),
			mchid:            kcgin.AppConfig.String("pingan.shWXmchid"),
			appId:            kcgin.AppConfig.String("pingan.shWXappid"),
			apiV3Key:         kcgin.AppConfig.String("pingan.shWXkey"),
			certUrl:          kcgin.AppConfig.String("pingan.shWXCertUrl"),
			certExpire:       kcgin.AppConfig.String("pingan.shWXCertExpire"),
		}
		return client, nil
	} else {
		err := errors.New("当前城市未开通平安支付通道配置")
		return nil, err
	}
}

const (
	FormatMessage       = "%s\n%s\n%d\n%s\n%s\n" //格式化信息
	NonceLength         = 32                     // 随机字符串的长度
	HeaderAuthorization = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\"," +
		"signature=\"%s\"" //Authorization信息
	Symbols              = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 生成随机字符串时依赖所有内容
	SignPayFormatMessage = "%s\n%s\n%s\n%s\n"
)

//发送post请求
func (c *WXClient) DoRequest(ctx context.Context, requestURL, reqBody, method string) ([]byte, error) {
	var err error
	var authorization string
	request, err := http.NewRequestWithContext(ctx, method, requestURL, strings.NewReader(reqBody))
	if err != nil {
		return nil, err
	}
	request.Header.Set("Accept", "*/*")
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("User-Agent", "WechatPay-Go-HttpClient/1.0")
	authorization, err = c.GetAuthorization(ctx, method, request.URL.RequestURI(), reqBody)
	if err != nil {
		return nil, fmt.Errorf("generate authorization err:%s", err.Error())
	}
	request.Header.Set("Authorization", authorization)
	httpClient := new(http.Client)
	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode == http.StatusOK {
		res, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}
		return res, nil
	} else {
		errBody,err :=  ioutil.ReadAll(response.Body)
		if err != nil {
			return nil,err
		}
		err = errors.New(string(errBody))
		return nil,err
	}
}

//获取Authorization头
func (c *WXClient) GetAuthorization(ctx context.Context, method, canonicalURL, signBody string) (authorization string, err error) {
	nonce, err := generateNonceStr()
	if err != nil {
		return "", err
	}
	timestamp := time.Now().Unix()
	message := fmt.Sprintf(FormatMessage, method, canonicalURL, timestamp, nonce, signBody)
	signature, err := c.Sign(ctx, message)
	if err != nil {
		return "", err
	}
	authorization = fmt.Sprintf(HeaderAuthorization, c.mchid, nonce, timestamp,
		c.wxSerialNo, signature)
	return authorization, nil
}

//请求随机串
func generateNonceStr() (string, error) {
	bytes := make([]byte, NonceLength)
	_, err := rand.Read(bytes)
	if err != nil {
		return "", err
	}
	symbolsByteLength := byte(len(Symbols))
	for i, b := range bytes {
		bytes[i] = Symbols[b%symbolsByteLength]
	}
	return string(bytes), nil
}

//签名
func (c *WXClient) Sign(ctx context.Context, message string) (signature string, err error) {
	privateKey, _ := c.LoadPrivateKey()
	h := crypto.Hash.New(crypto.SHA256)
	_, err = h.Write([]byte(message))
	if err != nil {
		return "", nil
	}
	hashed := h.Sum(nil)
	signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(signatureByte), nil
}

//加载私钥
func (c *WXClient) LoadPrivateKey() (privateKey *rsa.PrivateKey, err error) {
	privateKeyBytes, err := ioutil.ReadFile(c.wxPrivateKeyPath)
	if err != nil {
		return nil, fmt.Errorf("读取私钥证书失败 file err:%s", err.Error())
	}

	block, _ := pem.Decode(privateKeyBytes)
	if block == nil {
		return nil, fmt.Errorf("解码私钥失败")
	}
	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("解析私钥失败 err:%s", err.Error())
	}
	privateKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("非法私钥文件，检查私钥文件")
	}
	return privateKey, nil
}

//唤起app支付
func (c *WXClient)Pay(ctx context.Context,  requestURL, reqBody string)(upPay WXUpPay,err error)  {
	prePayres,err := c.DoRequest(context.Background(),requestURL,reqBody,http.MethodPost)
	if err != nil {
		return
	}
	var wxPrePay WXprepay
	if err = json.Unmarshal(prePayres, &wxPrePay); err != nil {
		return
	}
	nonceStr, err := generateNonceStr()
	if err != nil {
		return
	}
	timeStamp := strconv.FormatInt(time.Now().Unix(), 10)

	message := fmt.Sprintf(SignPayFormatMessage, c.appId, timeStamp, nonceStr, wxPrePay.PrepayId)
	sign, err := c.Sign(context.Background(), message)
	if err != nil {
		return
	}
	upPay = WXUpPay{
		Appid:     c.appId,
		Partnerid: c.mchid,
		Prepayid:  wxPrePay.PrepayId,
		Package:   "Sign=WXPay",
		Noncestr:  nonceStr,
		Timestamp: timeStamp,
		Sign:      sign,
	}
	return
}

//回调验签并解析数据
func (c *WXClient) Notify(bodyByte, headerByte []byte) (data string, err error) {
	if err = c.VerifyNotify(bodyByte, headerByte); err != nil {
		return "", err
	}
	var body WXNotify
	if err = json.Unmarshal(bodyByte, &body); err != nil {
		return "", err
	}
	data, err = c.DecryptToString(body.Resource.Associated_data, body.Resource.Nonce, body.Resource.Ciphertext)

	if err != nil {
		return "", err
	}
	return
}

//解密回调数据apiV3Key
func (c *WXClient) DecryptToString(associatedData, nonce, ciphertext string) (data string, err error) {
	decodedCiphertext, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}
	cip, err := aes.NewCipher([]byte(c.apiV3Key))
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(cip)
	if err != nil {
		return "", err
	}
	dataByte, err := gcm.Open(nil, []byte(nonce), decodedCiphertext, []byte(associatedData))
	if err != nil {
		return "", err
	}
	data = string(dataByte)
	return data, nil
}

//对回调进行验签
func (c *WXClient) VerifyNotify(bodyBytes, headerBytes []byte) (err error) {
	var header WXHeader
	err = json.Unmarshal(headerBytes, &header)
	signature, _ := base64.StdEncoding.DecodeString(strings.TrimSpace(header.WechatpaySignature))

	if err != nil {
		return
	}
	certMap, err := c.GetCert()
	if err != nil {
		return
	}
	cert, ok := certMap[header.WechatpaySerial]
	if !ok {
		return fmt.Errorf("no serial number:%s corresponding certificate ", header.WechatpaySerial)
	}
	certificate, err := LoadCertificate(cert)
	if err != nil {
		return
	}
	body := string(bodyBytes)
	message := fmt.Sprintf("%s\n%s\n%s\n", header.WechatpayTimestamp, header.WechatpayNonce, body)
	hashed := sha256.Sum256([]byte(message))
	err = rsa.VerifyPKCS1v15(certificate.PublicKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], signature)
	if err != nil {
		return fmt.Errorf("verifty signature with public key err:%s", err.Error())
	}
	return nil

}

//获取平台证书
func (c *WXClient) GetCert() (certMap map[string]string, err error) {
	key := fmt.Sprintf(constkey.WX_CERT, c.cid)
	res, err := redis.RedisGlobMgr.Get(key)
	if err != nil {
		return
	}
	if res != nil {
		resByte := res.([]byte)
		err = json.Unmarshal(resByte, &certMap)
		return
	} else {
		certMap, err = c.ReqCert()
		if err != nil {
			return nil, err
		}
		certByte, _ := json.Marshal(certMap)
		certExpire, _ := strconv.Atoi(c.certExpire)
		if certExpire <= 0 {
			certExpire = 36000
		}
		_ = redis.RedisGlobMgr.Set(key, string(certByte), int64(certExpire))
		return
	}
}

//请求微信获取平台证书
func (c *WXClient) ReqCert() (certMap map[string]string, err error) {
	resByte, err := c.DoRequest(context.Background(), c.certUrl, "", http.MethodGet)
	if err != nil {
		return
	}
	var certList WXGetCertRes
	err = json.Unmarshal(resByte, &certList)
	if err != nil {
		return
	}
	if len(certList.Data) <= 0 {
		return nil, fmt.Errorf("获取证书列表失败 res : %s", string(resByte))
	}
	certStr, err := c.DecryptToString(certList.Data[0].EncryptCertificate.Associated_data,
		certList.Data[0].EncryptCertificate.Nonce, certList.Data[0].EncryptCertificate.Ciphertext)

	certMap = map[string]string{
		certList.Data[0].SerialNo: certStr,
	}
	return
}

//  通过证书的文本内容加载证书
func LoadCertificate(certificateStr string) (certificate *x509.Certificate, err error) {
	block, _ := pem.Decode([]byte(certificateStr))
	if block == nil || block.Type != "CERTIFICATE" {
		return nil, fmt.Errorf("解码证书失败！")
	}
	certificate, err = x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("parse certificate err:%s", err.Error())
	}
	return certificate, nil
}
